//
//  MultiFileDownloader.swift
//  MultiFileDownloader
//
//  Created by Fanxx on 2021/10/25.
//  Copyright © 2021 JunYue. All rights reserved.
//

import Foundation
import Alamofire

public protocol MultiFileDownloaderDelegate: AnyObject {
    /// 状态变化
    func multiFileDownloader(_ downloader: MultiFileDownloader, statusChanged status: MultiFileDownloader.Status)
    /// 进度变化
    func multiFileDownloader(_ downloader: MultiFileDownloader, progressChanged progress: Float)
    /// 可定义错误
    func multiFileDownloader(_ downloader: MultiFileDownloader, isError response: HTTPURLResponse) -> Error?
}
extension MultiFileDownloaderDelegate {
    /// 默认400-500下载失败，其他继续下载
    public func multiFileDownloader(_ downloader: MultiFileDownloader, isError response: HTTPURLResponse) -> Error? {
        let code = response.statusCode
        if code >= 400 && code < 500 {
            return NSError.init(domain: "multi-file downloader", code: response.statusCode, userInfo: [NSLocalizedDescriptionKey: "状态码不对"])
        }
        return nil
    }
}

open class MultiFileDownloader {
    public enum Status {
        /// 下载中
        case downloading
        /// 暂停
        case paused
        /// 取消下载
        case canceled
        /// 下载完成
        case finished
        /// 下载失败
        case failed
    }
    ///用于操作当前下载文件索引，线程安全
    public struct DownloadIndex {
        private let _queue = DispatchQueue(label: "multi-file downloader file index queue", attributes: .concurrent)
        private var _value: Int {
            didSet {
                debugPrint("当前下载的文件数量: \(_value)")
            }
        }
        init(_ value: Int = 0) {
            self._value = value
        }
        /// 重置
        mutating func reset(_ value: Int = 0) {
            _queue.sync(flags: .barrier) { _value = value }
        }
        /// 获取索引
        func value() -> Int {
            return _queue.sync { _value }
        }
        /// 获取索引并移动到下一个
        mutating func move() -> Int {
            return _queue.sync(flags: .barrier) {
                let v = _value
                _value += 1
                return v
            }
        }
    }
    
    /// 待下载的文件链接列表，在开始下载后就不要设置了
    internal(set) public var downloadList: [MultiFileModelType]
    /// 下载路径
    public let downloadPath: URL
    /// 支持同时下载的文件数量
    open var maxRequest = 5
    
    public init(downloadList: [MultiFileModelType], downloadPath: URL, maxRequest: Int = 5) {
        self.downloadList = downloadList
        self.downloadPath = downloadPath
        self.maxRequest = maxRequest
    }
    //    deinit {
    //        debugPrint("下载结束")
    //    }
    
    /// 委托
    open weak var delegate: MultiFileDownloaderDelegate?
    /// 状态
    private(set) open var status: Status = .canceled {
        didSet {
            if oldValue != self.status {
                DispatchQueue.main.async { [weak self] in
                    guard let self = self else { return }
                    self.delegate?.multiFileDownloader(self, statusChanged: self.status)
                }
            }
        }
    }
    private(set) open var progress: Float = 0 {
        didSet {
            DispatchQueue.main.async { [weak self] in
                guard let self = self else { return }
                self.delegate?.multiFileDownloader(self, progressChanged: self.progress)
            }
        }
    }
    /// 总下载数量
    private var totalCount: Int = 0 {
        didSet {
            debugPrint("需要下载的文件总数量: \(self.totalCount)")
        }
    }
    /// 下一个准备下载的索引
    private var nextDownloadIndex = DownloadIndex(0)
    @AtomicProperty
    private var currentRequests: [Int: DownloadRequest] = [:] {
        didSet {
            debugPrint("当前同时下载数量: \(self.currentRequests.count)")
        }
    }
    /// 下载失败状态的错误信息
    private(set) open var failedError: Error?
    
    /// 开始下载
    open func start() {
        switch self.status {
        case .downloading, .finished:
            debugPrint("开始下载但本来就下载中或下载完成")
            DispatchQueue.main.async { [weak self] in
                guard let self = self else { return }
                self.delegate?.multiFileDownloader(self, statusChanged: self.status)
                self.delegate?.multiFileDownloader(self, progressChanged: self.progress)
            }
            return
        case .paused where self.totalCount > 0:
            debugPrint("继续下载")
            if self.totalCount > self.nextDownloadIndex.value() {
                self.status = .downloading
                self.downloadFile()
            }else{
                self.status = .finished
            }
        default:
            debugPrint("开始下载")
            self.unsafeStart()
        }
    }
    /// 直接开始下载，不判断下载状态，用于子类继承重载
    open func unsafeStart() {
        self.createDownloadDirectoryIfNotExist()
        DispatchQueue.global().async { [weak self] in
            guard let self = self else { return }
            self.status = .downloading
            self.totalCount = self.downloadList.count
            self.nextDownloadIndex.reset(0)
            self.currentRequests.removeAll()
            self.skipDownloadedFile()
            // 刚开始下载需要到算好下载数再发送进度，不然UI会出现从0跑起的现象
            self.updateProgress()
            self.downloadFile()
        }
    }
    open func createDownloadDirectoryIfNotExist() {
        if !FileManager.default.fileExists(atPath: self.downloadPath.path) {
            try? FileManager.default.createDirectory(at: self.downloadPath, withIntermediateDirectories: true, attributes: nil)
        }
    }
    /// 暂停下载
    open func pause() {
        if self.status == .downloading {
            self.status = .paused
        }
    }
    /// 取消下载并删除已下载文件
    open func cancel() {
        self.currentRequests.forEach({ $0.value.cancel() })
        self.status = .canceled
        if FileManager.default.fileExists(atPath: self.downloadPath.path) {
            try? FileManager.default.removeItem(at: self.downloadPath)
        } else {
            print("Could not find directory : \(self.downloadPath)")
        }
    }
    ///下载前将已下载的ts跳过
    private func skipDownloadedFile() {
        for i in 0..<self.totalCount {
            let file = self.downloadList[i]
            let path = self.downloadPath.appendingPathComponent("\(file.filename)")
            // 如果文件已经存在本地说明下载过了，不存在才需要下载
            if !FileManager.default.fileExists(atPath: path.path) {
                self.nextDownloadIndex.reset(i)
                break
            }
        }
    }
    private func downloadFile() {
        if self.status == .downloading && self.currentRequests.count < self.maxRequest {
            let index = self.nextDownloadIndex.move()
            if self.totalCount > index {
                let file = self.downloadList[index]
                let path = self.downloadPath.appendingPathComponent("\(file.filename)")
                // 如果文件已经存在本地说明下载过了，不存在才需要下载
                if !FileManager.default.fileExists(atPath: path.path) {
                    let destination: DownloadRequest.Destination = { _, _ in
                        return (path, [])
                    }
                    let request = AF.download(file.url, to: destination).response { [weak self] response in
                        guard let self = self else { return }
                        debugPrint(response)
                        //若非下载中则不再处理
                        if self.status != .downloading { return }
                        if let error = response.error {
                            // 下载失败直接中断
                            self.onFailed(error)
                        }else{
                            let error: Error?
                            if let response = response.response {
                                if let delegate = self.delegate {
                                    error = delegate.multiFileDownloader(self, isError: response)
                                }else if response.statusCode < 400 || response.statusCode >= 500{
                                    error = nil
                                }else{
                                    error = NSError.init(domain: "multi-file downloader", code: response.statusCode, userInfo: [NSLocalizedDescriptionKey: "状态码不对"])
                                }
                            }else{
                                error = NSError.init(domain: "multi-file downloader", code: 0, userInfo: [NSLocalizedDescriptionKey: "没有HTTP响应数据"])
                            }
                            if let error = error {
                                // 下载失败直接中断
                                self.onFailed(error)
                            }else{
                                self.currentRequests.removeValue(forKey: index)
                                self.updateProgress()
                                // 下载成功，继续下载下一个
                                self.downloadFile()
                            }
                        }
                    }
                    self.currentRequests[index] = request
                }
                /// 加完一个加下一个， 错开线程，免得栈溢出
                DispatchQueue.global().async { [weak self] in
                    self?.downloadFile()
                }
            }else{
                if self.currentRequests.isEmpty {
                    self.status = .finished
                }
            }
        }
    }
    private func updateProgress() {
        /// 如果是暂停中就先不更新进度，免得界面出现暂停后还跑进度
        if self.status == .downloading {
            self.progress = min(Float(self.nextDownloadIndex.value()) / Float(self.totalCount), 1)
        }
    }
    internal func onFailed(_ error: Error) {
        debugPrint("下载出错：\(error.localizedDescription)")
        self.currentRequests.forEach({ $0.value.cancel() })
        self.failedError = error
        self.status = .failed
    }
}

///多读单写
@propertyWrapper
private struct AtomicProperty<Value> {
    let queue = DispatchQueue(label: "Atomic write access queue", attributes: .concurrent)
    var value: Value
    
    public init(wrappedValue: Value) {
        self.value = wrappedValue
    }
    
    public var wrappedValue: Value {
        get {
            return queue.sync { value }
        }
        set {
            queue.sync(flags: .barrier) { value = newValue }
        }
    }
    
    public mutating func mutate(_ mutation: (inout Value) throws -> Void) rethrows {
        return try queue.sync(flags: .barrier) {
            try mutation(&value)
        }
    }
}
